/* sm3 extension for PHP */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "php.h"
#include "ext/standard/info.h"
#include "php_sm3.h"
#include "sm3.h"

/* For compatibility with older PHP versions */
#ifndef ZEND_PARSE_PARAMETERS_NONE
#define ZEND_PARSE_PARAMETERS_NONE() \
    ZEND_PARSE_PARAMETERS_START(0, 0) \
    ZEND_PARSE_PARAMETERS_END()
#endif


static unsigned char message_buffer[64] = {0};
static unsigned int hash[8] = {0};
static unsigned int T[64] = {0};

void out_hex() {
    unsigned int i = 0;
    for (i = 0; i < 8; i++) {
        printf("%08x ", hash[i]);
    }
    printf("\n");
}

void intToString(unsigned char *out_hash) {
    int i = 0;
    for (i = 0; i < 8; i++) {
        out_hash[i * 4] = (unsigned char) ((hash[i] >> 24) & 0xFF);
        out_hash[i * 4 + 1] = (unsigned char) ((hash[i] >> 16) & 0xFF);
        out_hash[i * 4 + 2] = (unsigned char) ((hash[i] >> 8) & 0xFF);
        out_hash[i * 4 + 3] = (unsigned char) ((hash[i]) & 0xFF);
    }
//    for (i = 0; i < 32; i++)
//        printf("%c", out_hash[i]);
//    printf("\n");

}

unsigned int rotate_left(unsigned int a, unsigned int k) {
    k = k % 32;
    return ((a << k) & 0xFFFFFFFF) | ((a & 0xFFFFFFFF) >> (32 - k));
}


int init_T() {
    int i = 0;
    for (i = 0; i < 16; i++) {
        T[i] = 0x79cc4519;
    }
    for (i = 16; i < 64; i++) {
        T[i] = 0x7a879d8a;
    }
    return 1;
}

unsigned int FF(X, Y, Z, j) {
    unsigned int ret = 0;
    if (0 <= j && j < 16) {
        ret = X ^ Y ^ Z;
    } else if (16 <= j && j < 64) {
        ret = (X & Y) | (X & Z) | (Y & Z);
    }
    return ret;
}

unsigned int GG(X, Y, Z, j) {
    unsigned int ret = 0;
    if (0 <= j && j < 16) {
        ret = X ^ Y ^ Z;
    } else if (16 <= j && j < 64) {
        ret = (X & Y) | ((~X) & Z);
    }
    return ret;
}

#define P_0(X) X ^ (rotate_left(X, 9)) ^ (rotate_left(X, 17))

#define P_1(X) X ^ (rotate_left(X, 15)) ^ (rotate_left(X, 23))

int CF(unsigned char *arr) {
    unsigned int W[68];
    unsigned int W_1[64];
    unsigned int j;
    unsigned int A, B, C, D, E, F, G, H;
    unsigned int SS1, SS2, TT1, TT2;
    for (j = 0; j < 16; j++) {
        W[j] = arr[j * 4 + 0] << 24 | arr[j * 4 + 1] << 16 | arr[j * 4 + 2] << 8 | arr[j * 4 + 3];
    }
    for (j = 16; j < 68; j++) {
        W[j] = P_1(W[j - 16] ^ W[j - 9] ^ (rotate_left(W[j - 3], 15))) ^ (rotate_left(W[j - 13], 7)) ^ W[j - 6];
    }
    for (j = 0; j < 64; j++) {
        W_1[j] = W[j] ^ W[j + 4];
    }
    A = hash[0];
    B = hash[1];
    C = hash[2];
    D = hash[3];
    E = hash[4];
    F = hash[5];
    G = hash[6];
    H = hash[7];
    for (j = 0; j < 64; j++) {
        SS1 = rotate_left(((rotate_left(A, 12)) + E + (rotate_left(T[j], j))) & 0xFFFFFFFF, 7);
        SS2 = SS1 ^ (rotate_left(A, 12));
        TT1 = (FF(A, B, C, j) + D + SS2 + W_1[j]) & 0xFFFFFFFF;
        TT2 = (GG(E, F, G, j) + H + SS1 + W[j]) & 0xFFFFFFFF;
        D = C;
        C = rotate_left(B, 9);
        B = A;
        A = TT1;
        H = G;
        G = rotate_left(F, 19);
        F = E;
        E = P_0(TT2);

    }

    hash[0] = (A ^ hash[0]);
    hash[1] = (B ^ hash[1]);
    hash[2] = (C ^ hash[2]);
    hash[3] = (D ^ hash[3]);
    hash[4] = (E ^ hash[4]);
    hash[5] = (F ^ hash[5]);
    hash[6] = (G ^ hash[6]);
    hash[7] = (H ^ hash[7]);
    return 1;
}

void SM3_Init() {
    init_T();
    hash[0] = 0x7380166f;
    hash[1] = 0x4914b2b9;
    hash[2] = 0x172442d7;
    hash[3] = 0xda8a0600;
    hash[4] = 0xa96f30bc;
    hash[5] = 0x163138aa;
    hash[6] = 0xe38dee4d;
    hash[7] = 0xb0fb0e4e;
}

void Block(unsigned char *msg, unsigned int msglen) {
    int i;
    int left = 0;
    unsigned long long total = 0;

    for (i = 0; i < msglen / 64; i++) {
        memcpy(message_buffer, msg + i * 64, 64);
        CF(message_buffer);
    }

    total = msglen * 8;
    left = msglen % 64;
    memset(&message_buffer[left], 0, 64 - left);
    memcpy(message_buffer, msg + i * 64, left);
    message_buffer[left] = 0x80;
    if (left <= 55) {
        for (i = 0; i < 8; i++)
            message_buffer[56 + i] = (total >> ((8 - 1 - i) * 8)) & 0xFF;
        CF(message_buffer);
    } else {
        CF(message_buffer);
        memset(message_buffer, 0, 64);
        for (i = 0; i < 8; i++)
            message_buffer[56 + i] = (total >> ((8 - 1 - i) * 8)) & 0xFF;
        CF(message_buffer);
    }

}

int SM3(unsigned char *msg, unsigned int msglen, unsigned char *out_hash) {
    SM3_Init();
    Block(msg, msglen);
//    out_hex();
    intToString(out_hash);
    return 1;
}


/* {{{ void sm3_test1()
 */
PHP_FUNCTION(sm3_test1)
        {
                ZEND_PARSE_PARAMETERS_NONE();

        php_printf("The extension %s is loaded and working!\r\n", "sm3");
        }
/* }}} */

/* {{{ string sm3_test2( [ string $var ] )
 */
PHP_FUNCTION(sm3_test2)
        {
                char *var = "World";
                size_t var_len = sizeof("World") - 1;
                zend_string *retval;

                ZEND_PARSE_PARAMETERS_START(0, 1)
                Z_PARAM_OPTIONAL
                Z_PARAM_STRING(var, var_len)
                ZEND_PARSE_PARAMETERS_END();

                retval = strpprintf(0, "Hello %s", var);

                RETURN_STR(retval);
        }

/* }}}*/

void outPrintf(unsigned char *c1, char *name) {
    php_printf("\r\n");
    php_printf("%s:\r\n  ", name);
    int i;
    for (i = 0; i < strlen((char *) c1); i++) {
        php_printf("%08x", c1[i]);
        if (((i + 1) % 4) == 0) php_printf(" ");
    }
    php_printf("\r\n");
}
PHP_FUNCTION(sm3)
        {
                unsigned char *input = NULL;
                int argc = ZEND_NUM_ARGS();
                size_t input_len;


                int i;
                unsigned char output[32];

                char str;
                char *result;
                char *ptr;

                if (zend_parse_parameters(argc, "s", &input, &input_len) == FAILURE)
                return;

                result = (char *) emalloc(64);
                ptr = result;
                SM3(input, input_len, output);
                for (i=0; i<32; i++)
                {
                    sprintf(&str, "%02x", output[i]);
                    memcpy(ptr, &str, 2);
                    ptr += 2;
                }
                *ptr = '\0';
                RETURN_STRINGL(result, 64);
        }


/* {{{ PHP_RINIT_FUNCTION
 */
PHP_RINIT_FUNCTION(sm3)
        {
#if defined(ZTS) && defined(COMPILE_DL_SM3)
                ZEND_TSRMLS_CACHE_UPDATE();
#endif

                return SUCCESS;
        }
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
 */
PHP_MINFO_FUNCTION(sm3)
        {
                php_info_print_table_start();
        php_info_print_table_header(2, "sm3 support", "enabled");
        php_info_print_table_end();
        }
/* }}} */

/* {{{ arginfo
 */
ZEND_BEGIN_ARG_INFO(arginfo_sm3_test1,
0)

ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO(arginfo_sm3_test2,
0)
ZEND_ARG_INFO(0, str)

ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_sm3, 0, 0, 1)
ZEND_ARG_INFO(0, input)
ZEND_END_ARG_INFO()
/* }}} */

/* {{{ sm3_functions[]
 */
        static const zend_function_entry sm3_functions[] = {
                PHP_FE(sm3_test1, arginfo_sm3_test1)
                PHP_FE(sm3_test2, arginfo_sm3_test2)
                PHP_FE(sm3, arginfo_sm3)
                PHP_FE_END
        };
/* }}} */

/* {{{ sm3_module_entry
 */
        zend_module_entry sm3_module_entry = {
                STANDARD_MODULE_HEADER,
                "sm3",                    /* Extension name */
                sm3_functions,            /* zend_function_entry */
                NULL,                            /* PHP_MINIT - Module initialization */
                NULL,                            /* PHP_MSHUTDOWN - Module shutdown */
                PHP_RINIT(sm3),            /* PHP_RINIT - Request initialization */
                NULL,                            /* PHP_RSHUTDOWN - Request shutdown */
                PHP_MINFO(sm3),            /* PHP_MINFO - Module info */
                PHP_SM3_VERSION,        /* Version */
                STANDARD_MODULE_PROPERTIES
        };
/* }}} */

#ifdef COMPILE_DL_SM3
# ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
# endif
ZEND_GET_MODULE(sm3)
#endif

